﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace ReporterEngine
{
    public class LexPath
    {
        internal enum LexKind
        {
            Unknown,        // Unknown lexeme
            Or,             // Operator 'or'
            And,            // Operator 'and'
            Eq,             // Operator '='
            Ne,             // Operator '!='
            Lt,             // Operator '<'
            Le,             // Operator '<='
            Gt,             // Operator '>'
            Ge,             // Operator '>='
            Plus,           // Operator '+'
            Minus,          // Operator '-'
            Multiply,       // Operator '*'
            Divide,         // Operator 'div'
            Modulo,         // Operator 'mod'
            UnaryMinus,     // Not used
            Union,          // Operator '|'
            LastOperator = Union,

            DotDot,         // '..'
            ColonColon,     // '::'
            SlashSlash,     // Operator '//'
            Number,         // Number (numeric literal)
            Axis,           // AxisName

            Name,           // NameTest, NodeType, FunctionName, AxisName, second part of VariableReference
            String,         // Literal (string literal)
            Eof,            // End of the expression

            FirstStringable = Name,
            LastNonChar = Eof,

            LParens = '(',
            RParens = ')',

            LBracket = '[',
            RBracket = ']',

            Dot = '.',
            At = '@',
            Comma = ',',

            Star = '*',      // NameTest
            Slash = '/',      // Operator '/'
            Dollar = '$',      // First part of VariableReference

            RBrace = '}',      // Used for AVTs
        };



        //public List<TemplateParameter> GetParameters()
        //{
        //    var result = new List<TemplateParameter>();

        //    IterateAll(this.n, result);

        //    return result;
        //}

        //void IterateAll(PathToken node, List<TemplateParameter> parameters)
        //{
           

        //    foreach (var innerNode in node..Childs)
        //    {
        //        IterateAll(innerNode, parameters);
        //    }
        //}



        private string pathExpr;

        public bool HasError;
        //cached paths
        public static Dictionary<string,LexPath> CachedPaths = new Dictionary<string, LexPath>();

        public static LexPath Scan(string path)
        {
            //replace path parameters

            if (CachedPaths.ContainsKey(path))
                return CachedPaths[path];

            var result = new LexPath();

            result.pathExpr = path;
            result.StartToken = result.NewToken();

            result._curToken = result.StartToken;

            result.curChar = path[0];
//#if !test
            try
            {
//#endif
                while (result.kind != LexKind.Eof)
                {
                    result.NextLex();
                }

//#if !test
            }
            catch (Exception)
            {
                result.HasError = true;
            }
//#endif
            CachedPaths.Add(path,result);

            return result;
        }

        public int LastIndex
        {
            get { return curIndex; }
        }

        private char curChar;
        private int curIndex;
        LexKind kind = LexKind.Unknown;
        int lexStart;
        int lexSize;
        int prevLexEnd;
        LexKind prevKind;


        private void GotoNextChar()
        {
            Debug.Assert(-1 <= curIndex && curIndex < pathExpr.Length);
            curIndex++;
            if (curIndex < pathExpr.Length)
            {
                curChar = pathExpr[curIndex];
            }
            else
            {
                Debug.Assert(curIndex == pathExpr.Length);
                curChar = '\0';
            }
        }

        private void SkipSpace()
        {
            while (IsWhiteSpace(curChar))
            {
                GotoNextChar();
            }
        }

        char SecondChar()
        {
            if (curIndex + 1 < pathExpr.Length)
            {
                return pathExpr[curIndex + 1];
            }
            else
            {
                //Debug.Assert(curIndex == pathExpr.Length);
                return '\0';
            }
        }

        char ThirdChar()
        {
            if (curIndex + 2 < pathExpr.Length)
            {
                return pathExpr[curIndex + 2];
            }
            else
            {
                //Debug.Assert(curIndex == pathExpr.Length);
                return '\0';
            }
        }

        char FourthChar()
        {
            if (curIndex + 3 < pathExpr.Length)
            {
                return pathExpr[curIndex + 3];
            }
            else
            {
                Debug.Assert(curIndex == pathExpr.Length);
                return '\0';
            }
        }

        //private static bool IsAsciiDigit(char ch)
        //{
        //    return (uint)(ch - '0') <= 9;
        //}

        public static bool IsWhiteSpace(char ch)
        {
            return ch <= ' ' && (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');
        }

        private PathToken _curToken;
        public PathToken StartToken;
        public Stack<PathToken> TokenStack = new Stack<PathToken>();
        public Stack<PathToken> ExpressionStack = new Stack<PathToken>();

        public int ErrorPosition;


        public void NextLex()
        {
            prevLexEnd = curIndex;
            prevKind = kind;
            SkipSpace();
            lexStart = curIndex;

            //if (curToken.Kind == TokenKind.Undefined && curToken.PathExp == null)
            //{
            //    switch (curChar)
            //    {
            //        case '0':
            //        case '1':
            //        case '2':
            //        case '3':
            //        case '4':
            //        case '5':
            //        case '6':
            //        case '7':
            //        case '8':
            //        case '9':
            //            if (curToken.Kind == TokenKind.Undefined)
            //                curToken.Kind = TokenKind.Number;
            //            //ScanNumber();
            //            break;
            //    }

            //    GotoNextChar();
            //}

            switch (curChar)
            {
                    //check and , or and skip chars
                case 'a':
                    if (SecondChar() == 'n' && ThirdChar() == 'd' && FourthChar() == ' ')
                    {
                        //get current expression from stack
                        var currentstack = ExpressionStack.Pop();

                        //add to expression stack
                        var subToken = NewToken(new PathToken {StartPosition = curIndex, EndPosition = curIndex + 3});
                        subToken.Kind = TokenKind.ConditionAnd;
                        //set next exp token and, or
                        currentstack.Next = subToken;

                        ExpressionStack.Push(subToken);
                        //set left and after set right

                        var leftToken = NewToken();
                        subToken.Left = leftToken;

                        _curToken = leftToken;


                        GotoNextChar();
                        GotoNextChar();
                        GotoNextChar();
                    }
                    break;

                case 'o':
                    if (SecondChar() == 'r' && ThirdChar() == ' ' && ExpressionStack.Count > 0)
                    {
                        //get current expression from stack
                        var currentstack = ExpressionStack.Pop();

                        //add to expression stack
                        var subToken = NewToken(new PathToken { StartPosition = curIndex, EndPosition = curIndex + 2 });
                        subToken.Kind = TokenKind.ConditionOr;
                        //set next exp token and, or
                        currentstack.Next = subToken;

                        ExpressionStack.Push(subToken);
                        //set left and after set right

                        var leftToken = NewToken();
                        subToken.Left = leftToken;

                        _curToken = leftToken;

                        GotoNextChar();
                        GotoNextChar();
                    }
                    break;
            }

            SkipSpace();

            switch (curChar)
            {
                case '\0':
                    kind = LexKind.Eof;
                    return;

                case '>':
                    if (SecondChar() == '>')
                    {
                        if (_curToken.PathExp != null || _curToken.Kind != TokenKind.Undefined)
                        {
                            var newToken = NewToken();
                            _curToken.Next = newToken;
                            _curToken = newToken;
                        }

                        GotoNextChar();
                    }
                    else
                    {
                        if (ExpressionStack.Count > 0)
                        {
                            var expToken = SetExpTokenWithRightToken(TokenOperator.Gt);
                        }
                        else
                        {
                            //error if missing < in path
                            ErrorPosition = curIndex;
                        }
                    }
                    break;

                case '<':
                    if (SecondChar() == '<')
                    {
                        if (_curToken.PathExp != null || _curToken.Kind != TokenKind.Undefined)
                        {
                            var newToken = NewToken();
                            newToken.Kind = TokenKind.UsedByReference;
                            _curToken.Next = newToken;
                            _curToken = newToken;
                        }

                        _curToken.Kind = TokenKind.UsedByReference;

                        GotoNextChar();
                    }
                    else
                    {
                        if (ExpressionStack.Count > 0)
                        {
                            var expToken = SetExpTokenWithRightToken(TokenOperator.Lt);
                        }
                        else
                        {
                            //error if missing < in path
                            ErrorPosition = curIndex;
                        }
                    }
                    break;

                case '[':
                    ErrorOnParseCompleted();

                    if (_curToken.PathExp != null || _curToken.Kind != TokenKind.Undefined)
                    {
                        var newSubToken = NewToken();
                        _curToken.Next = newSubToken;
                        _curToken = newSubToken;
                    }

                    _curToken.StartPosition = curIndex;
                    _curToken.Kind = TokenKind.Condition;

                    TokenStack.Push(_curToken);

                    //add to expression stack
                    var subToken = NewToken();
                    subToken.Kind = TokenKind.ConditionStart;
                    _curToken.SubToken = subToken;

                    ExpressionStack.Push(subToken);
                    //set left and after set right

                    var leftToken = NewToken();
                    subToken.Left = leftToken;

                    _curToken = leftToken;

                    break;

                case ']':
                    ErrorOnParseCompleted();

                    var backToken = TokenStack.Pop();
                    _curToken = backToken;

                    //write end position
                    _curToken.EndPosition = curIndex;

                    ExpressionStack.Pop();


                    if (_curToken.PathExp != null || _curToken.Kind != TokenKind.Undefined)
                    {
                        var newSubToken = NewToken(1);
                        _curToken.Next = newSubToken;
                        _curToken = newSubToken;
                    }

                    break;

                case '(':
                    ErrorOnParseCompleted();

                    _curToken.Kind = TokenKind.Method;

                    TokenStack.Push(_curToken);

                    var newArgToken = NewToken();
                    _curToken.Args.Add(newArgToken);
                    _curToken = newArgToken;
                    break;

                case ')':
                    ErrorOnParseCompleted();

                    var backArgToken = TokenStack.Pop();
                    _curToken = backArgToken;

                    //write end position
                    _curToken.EndPosition = curIndex;

                    _curToken.IsParseCompleted = true;

                    //if (curToken.PathExp != null || curToken.Kind != TokenKind.Undefined)
                    //{
                    //    var newSubToken = NewToken(1);
                    //    curToken.Next = newSubToken;
                    //    curToken = newSubToken;
                    //}
                    break;
                    
                case '”':
                case '“':
                case '„':
                case '"':
                    ErrorOnParseCompleted();

                    _curToken.StartPosition = curIndex;
                    _curToken.Kind = TokenKind.String;

                    var endpositions = new List<int>()
                                           {
                                               pathExpr.IndexOf('"', curIndex + 1),
                                               pathExpr.IndexOf('“', curIndex + 1),
                                               pathExpr.IndexOf('”', curIndex + 1),
                                               pathExpr.IndexOf('„', curIndex + 1)
                                           };

                    var endpos = endpositions.Where(x => x >= 0).Min();
                   

                    var subText = pathExpr.Substring(curIndex + 1, endpos - curIndex - 1);

                    _curToken.PathExp += pathExpr.Substring(curIndex, endpos - curIndex + 1);
                    ;

                    curIndex = endpos;

                    //end position
                    _curToken.EndPosition = endpos;

                    break;

                case '\'':
                case '’':
                case '‚':
                case '‘':
                    ErrorOnParseCompleted();

                    _curToken.Kind = TokenKind.Property;

                     var endTpositions = new List<int>()
                                           {
                                               pathExpr.IndexOf('\'', curIndex + 1),
                                               pathExpr.IndexOf('‘', curIndex + 1),
                                               pathExpr.IndexOf('’', curIndex + 1)
                                           };


                    var endNamepos = endTpositions.Where(x => x >= 0).Min();
                    var subNameText = pathExpr.Substring(curIndex + 1, endNamepos - curIndex - 1);

                    _curToken.PathExp += pathExpr.Substring(curIndex, endNamepos - curIndex + 1);

                    curIndex = endNamepos;
                    break;

                case '@':
                    _curToken.Kind = TokenKind.Parameter;

                    break;

                case ',':


                    if (_curToken.PathExp != null || _curToken.Kind != TokenKind.Undefined)
                    {
                        var newSubToken = NewToken();
                        _curToken.Next = newSubToken;
                        newSubToken.IsNextArg = true;
                        _curToken = newSubToken;
                    }

                    break;

                case '*':
                    //*=
                    if (SecondChar() == '=')
                    {
                        PathToken expToken = SetExpTokenWithRightToken(TokenOperator.Contains);
                    }
                        //* numbers
                    else
                    {

                    }
                    break;

                case '^':
                    //*=
                    if (SecondChar() == '=')
                    {
                        PathToken expToken = SetExpTokenWithRightToken(TokenOperator.StartWith);
                    }
                    //* numbers
                    else
                    {

                    }
                    break;

                case '=':
                    //*=
                    if (SecondChar() == '=')
                    {
                        PathToken expToken = SetExpTokenWithRightToken(TokenOperator.Eq);
                    }
                        //* numbers
                    else
                    {

                    }
                    break;

                case '!':
                    //*=
                    if (SecondChar() == '=')
                    {
                        PathToken expToken = SetExpTokenWithRightToken(TokenOperator.Ne);
                    }
                    //* numbers
                    else
                    {

                    }
                    break;



                default:
                    ErrorOnParseCompleted();
                    
                    if (_curToken.PathExp == null && _curToken.Kind == TokenKind.Undefined)
                        _curToken.Kind = TokenKind.Property;

                    _curToken.PathExp += curChar;
                    break;
            }

            if (ErrorPosition != 0)
            {
                this.kind = LexKind.Eof;
                return;
            }

            GotoNextChar();
        }

        PathToken NewToken()
        {
            return new PathToken { LexPath = this, StartPosition = curIndex};
        }

        PathToken NewToken(int addPosition)
        {
            return new PathToken { LexPath = this, StartPosition = curIndex + addPosition };
        }

        PathToken NewToken(PathToken token)
        {
            token.LexPath = this;
            return token;
        }

        void ErrorOnParseCompleted()
        {
            if (_curToken.IsParseCompleted)
                ErrorPosition = curIndex;
        }

        private PathToken SetExpTokenWithRightToken(TokenOperator op)
        {
            //goto next character
            GotoNextChar();

            var expToken = ExpressionStack.Peek();

            expToken.Operator = op;

            var rightToken = NewToken();

            expToken.Right = rightToken;

            _curToken = rightToken;
            return expToken;
        }

        public class PathToken
        {
            public string PathExp;
            public TokenKind Kind = TokenKind.Undefined;
            public PathToken Next;
            
            public List<PathToken> Args = new List<PathToken>();

            public PathToken SubToken;

            public PathToken Left;
            public PathToken Right;
            public TokenOperator Operator = TokenOperator.Undefined;

            public int StartPosition;
            public int EndPosition;

            public LexPath LexPath;

            public bool IsParseCompleted;

            public bool IsNextArg;

            public string StartEndString
            {
                get
                {
                    if (LexPath == null)
                        return "missing lexpath";

                    var result = LexPath.pathExpr;

                    if (EndPosition == 0 && Next != null)
                    {
                        EndPosition = Next.StartPosition;
                    }

                    if(EndPosition == 0)
                    {
                        result = result.Substring(StartPosition);
                    }

                    if (EndPosition != 0)
                    {
                        result = result.Substring(StartPosition,EndPosition - StartPosition + 1);
                    }

                    return result;
                }
            }
        }

        public enum TokenOperator
        {
            Undefined,
            Eq,             // Operator '=='
            Ne,             // Operator '!='
            Lt,             // Operator '<'
            Le,             // Operator '<='
            Gt,             // Operator '>'
            Ge,             // Operator '>='
            Contains,       // *=
            EndWith,        // $=
            StartWith       // ^=
        }

        public enum TokenKind
        {
            Undefined,
            Property,
            UsedByReference,
            Method,
            Type,
            Condition,
            ConditionStart,
            ConditionAnd,
            ConditionOr,
            Constant,
            String,
            Number,
            Parameter
        }
    }
}
